/**
 * \file Bottin.cpp
 * \brief Le code des opérateurs du bottin téléphonique
 * \author Pierre-Marc Levasseur, Simon Rodier
 * \version 0.1
 * \date juillet 2014
 *
 */

#include "Bottin.h"
#include <cmath>
#include <sstream>
#include <algorithm>
#include <ctype.h>

using namespace std;

namespace TP2P2
{

	/**
	 * \fn Bottin::Bottin
	 *
	 * \param [in] size: grandeur des tables de dispersion
	 */
	Bottin::Bottin(int size)
	:tabEntree(), tabNomPrenom(nextPrime(size*1.3)), tabTelephone(nextPrime(size*1.3)), cpt(0)
	{}

	/**
	*  \fn Bottin::Bottin(std::ifstream& file, int& nbCollisions)
	*  \param[in] file : un fichier contenant les données d'un bottin
	*/
	Bottin::Bottin(std::ifstream& file, int& nbCollisions) : cpt(0)
	{
		//il faut faire le tour du fichier
		std::string ligne;
		getline ( file, ligne );
		std::string nbEntrees;
		//la premiere ligne est un nombre: on doit l'utiliser comme taille de tabEntree
		//si la première ligne ne contient pas le nombre d'entrées, on lance une exception
		for (unsigned int i = 0 ; i < ligne.size() ; i++)
		{
			if (isdigit(ligne.at(i)))
			nbEntrees.push_back(ligne.at(i));
		}
		int nombreEntrees;
		std::istringstream convert(nbEntrees, ios_base::in);
		convert >> nombreEntrees;

		//maintenant qu'on a le bon nombre de cases requises,
		//il faut créer les trois vecteurs: tabEntree, tabTelephone et tabNomPrenom
		tabNomPrenom.resize(nextPrime(nombreEntrees*1.3));
		tabTelephone.resize(nextPrime(nombreEntrees*1.3));


		while (!file.eof())
		{

			//utiliser find pour découper la string
			getline ( file, ligne ) ;
			//maintenant, chaque ligne est de format
			//Nom, Prenom(Peut Contenir Espaces) '\t'	(999) 999-9999 '\t'	(888) 888-8888 '\t'	courriel '\n'

			//iterateurs de string pour saisir le Nom
			string::iterator premierCaractere = ligne.begin();
			string::iterator delimiteur = find(ligne.begin(), ligne.end(), ',');
			std::string nom(premierCaractere, delimiteur);

			//on mets les iterateurs autour du prénom
			premierCaractere = delimiteur;
			premierCaractere = premierCaractere+2; //on doit sauter par dessus l'espace pour se rendre à la prochaine lettre
			delimiteur = find(premierCaractere, ligne.end(), '\t');
			std::string prenom(premierCaractere, delimiteur);

			//on mets les itérateurs autour du numéro de téléphone
			premierCaractere = delimiteur;
			premierCaractere++;
			delimiteur = find(premierCaractere, ligne.end(), '\t');
			std::string telephone(premierCaractere, delimiteur);
			if (!checkTelephone(telephone))
			{
				try
				{
					telephone = convertTelephone(telephone);
				}
				catch (invalid_argument& ia)
				{
					throw invalid_argument("Constructuer(fichier): le format du numéro de téléphone est invalide.");
				}
			}


			//on mets les itérateurs autour du numéro de fax
			premierCaractere = delimiteur;
			premierCaractere++;
			delimiteur = find(premierCaractere, ligne.end(), '\t');
			std::string fax(premierCaractere, delimiteur);

			//on mets les itérateurs autour du numéro de fax
			premierCaractere = delimiteur;
			premierCaractere++;
			delimiteur = find(premierCaractere, ligne.end(), '\t');
			std::string courriel(premierCaractere, delimiteur);

			//maintenant qu'on a les infos, ajouter la personne au bottin
			ajouterPersonne(nom, prenom, telephone, fax, courriel, nbCollisions);
		}
	}


	/**
	 *  \fn Bottin::trouverAvecTelephone(const std::string& telephone)
	 *  \param[in] telephone: le numéro de téléphone que cherche dans la table
	 *  \return un vecteur de strings contenant le nom, prenom, numéro de fax et courriel de la personne
	 */
	std::vector<std::string> Bottin::trouverAvecTelephone(const std::string& telephone) const
	{
		std::string telephoneFormatAlternatif;
		if(!checkTelephone(telephone))
		{
			try
			{
				telephoneFormatAlternatif = convertTelephone(telephone);
			}
			catch (invalid_argument& ia)
			{
				throw invalid_argument("trouverAvecTelephone: numéro de téléphone invalide.");
			}
		}

		cout<<tabEntree.size()<<endl;
		//trouver la clé hash selon le numéro de téléphone
		int collisions = 0;
		int positionTab;
		if(telephoneFormatAlternatif.size()==14)
		{
			positionTab = hashTelephone(telephoneFormatAlternatif, collisions);
		}
		else
		{
			positionTab = hashTelephone(telephone, collisions);
		}

		//si la position est vide ou non-active, on lance une exception (personne absente du bottin)

		if (tabTelephone[positionTab] == NULL || tabTelephone[positionTab]->etat!=ACTIVE)
		{
			throw logic_error("trouverAvecTelephone : La personne recherchée n'est pas dans le bottin.");
		}
		positionTab = tabTelephone[positionTab]->position;
		vector<string> infos;

		//on ajoute les informations de la table à infos
		infos.push_back(tabEntree[positionTab].nom);
		infos.push_back(tabEntree[positionTab].prenom);
		infos.push_back(tabEntree[positionTab].fax);
		infos.push_back(tabEntree[positionTab].courriel);

		return infos;
	}

	/**
	 * \fn Bottin::trouverAvecNomPrenom(const string&, const string&) const
	 *
	 * \param [in] nom, prenom: nom et prénom de la personne qu'on cherche dans le bottin
	 *
	 * \return un vector<string> contenant dans l'ordre le numéro de téléphone, le numéro de fax et le courriel de la personne recherchée.
	 */
	vector<string> Bottin::trouverAvecNomPrenom(const string& nom, const string& prenom) const
	{
		int temp = 0;
		int positionTab = hashNomPrenom(nom, prenom, temp);

		if(tabNomPrenom[positionTab] == NULL || tabNomPrenom[positionTab]->etat != ACTIVE)
			throw logic_error("trouverAvecNomPrenom: Cette personne n'est pas présente dans le bottin.");

		positionTab = tabNomPrenom[positionTab]->position;

		vector<string> retour;

//		cout << tabEntree[positionTab].courriel << endl;

		retour.push_back(tabEntree[positionTab].tel);
		retour.push_back(tabEntree[positionTab].fax);
		retour.push_back(tabEntree[positionTab].courriel);

		return retour;
	}

	/**
	 * \fn Bottin::ajouterPersonne(const string&, const string&, const string&, const string&,
	 * 							const string&, int&)
	 *
	 * \param [in] nom, prenom, telephone, fax, adresseCourrriel: informations sur la personne à ajouter
	 * \param [out] nbCollisions: entier auquel on va ajouter le nombre de collisions qu'il y a eu lieu pour l'insertion de la personne
	 */
	void Bottin::ajouterPersonne(const string& nom, const string& prenom, const string& telephone,
			const string& fax, const string& adresseCourriel, int& nbCollisions)
	{
		std::string telephoneFormatAlternatif;
		if(!checkTelephone(telephone))
		{
			try
			{
				telephoneFormatAlternatif = convertTelephone(telephone);
			}
			catch (invalid_argument& ia)
			{
				throw invalid_argument("ajouterPersonne: numéro de téléphone invalide.");
			}
		}

		if (telephoneFormatAlternatif.size()==14)
		{
			Entree nouvellePersonne(nom, prenom, telephoneFormatAlternatif, fax, adresseCourriel);
			_ajouterEntree(nouvellePersonne, nbCollisions);
		}
		else
		{
			Entree nouvellePersonne(nom, prenom, telephone, fax, adresseCourriel);
			_ajouterEntree(nouvellePersonne, nbCollisions);
		}
		return;
	}

	Bottin::~Bottin()
	{
		//il faut passer dans le vecteur tabNomPrenom et effacer les pointeurs
		for (unsigned int i = 0; i < tabNomPrenom.size() ; i++)
		{
			delete tabNomPrenom[i];
			tabNomPrenom[i] = NULL;
		}

		tabNomPrenom.erase(tabNomPrenom.begin(), tabNomPrenom.end());
		tabNomPrenom.clear();


		//il faut passer dans le vecteur tabTelephone et effacer les pointeurs
		for (unsigned int i = 0; i < tabTelephone.size() ; i++)
		{
			//on a deja effacé tous les EntreeHashTab, alors on mets juste les pointeurs à NULL
			tabTelephone[i]= NULL;
		}
		tabTelephone.erase(tabTelephone.begin(), tabTelephone.end());
		tabTelephone.clear();

		//on efface le contenu de tabEntree
		tabEntree.clear();

		return;
	}

	/**
	 *  \fn Bottin::contains(const std::string& telephone)
	 *  \param[in] telephone - le numéro de téléphone qu'on tente de trouver
	 *  \return un booléen: vrai si le numéro de tél. est dans la table, faux sinon
	 */
	bool Bottin::contains(const std::string& telephone) const
	{
		std::string telephoneFormatAlternatif;
		if(!checkTelephone(telephone))
		{
			try
			{
				telephoneFormatAlternatif = convertTelephone(telephone);
			}
			catch (invalid_argument& ia)
			{
				throw invalid_argument("contains(telephone): numéro de téléphone invalide.");
			}
		}
		int collisions;
		int positionTabTelephone;
		if(telephoneFormatAlternatif.size()!=0)
		{
			positionTabTelephone = hashTelephone(telephoneFormatAlternatif, collisions);
		}
		else
		{
			positionTabTelephone = hashTelephone(telephone, collisions);
		}

		if(tabTelephone[positionTabTelephone] != NULL && tabTelephone[positionTabTelephone]->etat==ACTIVE &&
				(tabTelephone[positionTabTelephone]->tel==telephone || tabTelephone[positionTabTelephone]->tel == telephoneFormatAlternatif))
			return true;
		else
			return false;
	}


	/**
	 * \fn Bottin::contains(const string&, const string&)
	 *
	 * \param [in] nom, prenom : nom et prénom de la personne qu'on cherche dans la table de dispersion
	 *
	 * \return true si la personne est dans la table de dispersion, false sinon
	 */
	bool Bottin::contains(const string& nom, const string& prenom) const
	{
		int temp = 0;
		int positionTabNomPrenom = hashNomPrenom(nom, prenom, temp);

		if(tabNomPrenom[positionTabNomPrenom] != NULL && tabNomPrenom[positionTabNomPrenom]->etat == ACTIVE)
			return true;
		else
			return false;
	}

	/**
	 * \fn Bottin::estVide() const
	 * \return renvoie true si le bottin est vide, false sinon
	 */
	bool Bottin::estVide() const
	{
		return cpt == 0;
	}

	/**
	 * \fn Bottin::size() const
	 * \return renvoie le nombre de personne dans le bottin.
	 */
	int Bottin::size() const
	{
		return cpt;
	}

	/**
	 * \fn Bottin::_ajouterEntree(Entree&, int&)
	 *
	 * \param [in] personne: Entree à ajouter dans les tables de dispersion
	 * \param [out] nbCollisions: entier auquel on va ajouter le nombre de collisions qu'il y a eu lieu pour l'insertion de la personne
	 */
	void Bottin::_ajouterEntree(Entree& personne, int& nbCollisions)
	{
		// insertion dans le tableau principal
		tabEntree.push_back(personne);
		cpt++;
		// récupération des positions pour les tables de dispersion
		int positionTabNomPrenom = hashNomPrenom(personne.nom, personne.prenom, nbCollisions);
		int positionTabTelephone = hashTelephone(personne.tel, nbCollisions); //originalement commentée

		if(tabNomPrenom[positionTabNomPrenom] != NULL && tabNomPrenom[positionTabNomPrenom]->etat == ACTIVE &&
				( tabNomPrenom[positionTabNomPrenom]->nom == personne.nom && tabNomPrenom[positionTabNomPrenom]->prenom == personne.prenom
						&& tabNomPrenom[positionTabNomPrenom]->tel == personne.tel) )
				//ajouté 3eme condition - simon (nom, prenom et tel sont tous pareils)
			throw logic_error("_ajouterEntree: la personne est déjà dans le bottin.");

		// insertion d'une nouvelle entrée dans les deux tables de dispersion

		EntreeHashTab* nouvelleEntree = new EntreeHashTab(cpt - 1, personne.tel, personne.nom, personne.prenom, ACTIVE);

		tabNomPrenom[positionTabNomPrenom] = nouvelleEntree;
		tabTelephone[positionTabTelephone] = nouvelleEntree; //originalement commentée

		//rehash tabNomPrenom
		if(((double)cpt/(double)tabNomPrenom.size()) * 100.0 >= TAUX_MAX)
			rehashTabNomPrenom(nbCollisions);
		//rehash tabTelephone
		if(((double)cpt/(double)tabTelephone.size()) * 100 >= TAUX_MAX)  //originalement commentée
			rehashTabTelephone(nbCollisions);  //originalement commentée

//		cout<<"end add"<<endl;
		return;
	}

	/**
	 * \fn Bottin::hashNomPrenom(const string&, const string&, int&)
	 *
	 * \param [in] nom, prenom: nom et prénom de la personne qu'on veut placer dans la table de dispersion
	 * \param[out] nbCollisions: entier dans lequel on va compter le nombre de collisions
	 *
	 * \return position correspondant au nom et prénom fournis en argument
	 */
	int Bottin::hashNomPrenom(const string& nom, const string& prenom, int& nbCollisions) const
	{
		// on trouve une position en fonction du nom et prenom passé en arguement
		unsigned long position = 0;
		for(size_t i = 0; i < nom.size(); i++)
		{
			position += nom[i] * (int)pow(17, i);
//			position = position % tabNomPrenom.size();
		}


		position = position % tabNomPrenom.size();
		for(size_t i = 0; i < prenom.size(); i++)
		{
			position += prenom[i] * (int)pow(13, i);
//			position = position % tabNomPrenom.size();
		}
		position = position % tabNomPrenom.size();

		// gestion des collisions par sondage quadratique
		int j = 0;
		while(tabNomPrenom[position] != NULL &&
				(tabNomPrenom[position]->nom != nom && tabNomPrenom[position]->prenom != prenom && tabNomPrenom[position]->etat == ACTIVE))
		{
//			cout<<"Collision nom"<<nom<<", "<<prenom<<endl;
			nbCollisions++;
			j = (int)pow(-1, j) * (int)pow(j/2, 2);
			j++;
			position = position + j;
			while(position < 0)
				position += tabNomPrenom.size();
			while(position >= tabNomPrenom.size())
				position -= tabNomPrenom.size();
		}

		return position;
	}

	int Bottin::hashTelephone(const std::string& telephone, int& nbCollisions) const
	{
		std::string telephoneFormatAlternatif = telephone;
		if(!checkTelephone(telephoneFormatAlternatif))
		{
			try
			{
				telephoneFormatAlternatif = convertTelephone(telephone);
			}
			catch (invalid_argument& ia)
			{
				throw invalid_argument("numéro de téléphone invalide.");
			}
		}

		//on trouve une position dans la table de dispersion
		unsigned long position = 0;
		size_t i = 1; //iterateur pour boucler dans le numéro de téléphone
		//prenons les trois chiffres du code regional (*999*) 999-9999 indice 1 à 3
		while (i <=3 )
		{
			//position += chiffre*(int)pow(11,iteration)
			position += telephoneFormatAlternatif.at(i)*(size_t)pow(73,i-1);
//			position = position % tabTelephone.size();
			i++;
		}
		i = 6;
		//prenons les trois chiffres suivants (999) *999*-9999 indice 6 à 8
		while (i>=6 && i<=8)
		{
			//position += chiffre*(int)pow(13,iteration)
			position = position + telephoneFormatAlternatif.at(i)*(size_t)pow(131,i-6);
//			position = position % tabTelephone.size();
			i++;
		}
		i = 10;
		//prenons les quatres derniers chiffres (999) 999-*9999* indice 10 à 13
		while (i>=10 && i<=13)
		{
			//position += chiffre*(int)pow(17,iteration)
			position += telephoneFormatAlternatif.at(i)*(size_t)pow(79,i-10);
//			position = position % tabTelephone.size();
			i++;
		}
		position = position % tabTelephone.size();
		//gestion des collisions par sondage quadratique
		int compteur = 0;
		//pendant que la position de tabTelephone n'est pas null et que le telephone est différent de celui qu'on traite et que l'entree est active
		while(tabTelephone[position]!=NULL && tabTelephone[position]->etat==ACTIVE && tabTelephone[position]->tel!=telephone
				&& tabTelephone[position]->tel!=telephoneFormatAlternatif)
		{
//			cout<<"Collision tel "<<telephone<<endl;
			//augmenter le nombre de collisions
			nbCollisions++;
			compteur = (int)pow(-1, compteur) * (int)pow(compteur/2, 2);
			compteur++;
			position = position + compteur;
			while(position < 0)
			{
				position = position + tabTelephone.size();
			}
			while(position >= tabTelephone.size())
			{
				position -= tabTelephone.size();
			}
		}
		//on retourne la position
		return position;
	}

	/**
	 * \fn Bottin::rehashTabNomPrenom(int&)
	 *
	 * \param [in] nbCollision: entier dans lequel on va compter le nombre de collisions
	 */
	void Bottin::rehashTabNomPrenom(int& nbCollsions)
	{
		vector<EntreeHashTab*> oldArray = tabNomPrenom;

		// on agrandit la table de dispersion
		tabNomPrenom.resize(nextPrime(2 * oldArray.size()));
		for(size_t i = 0; i < tabNomPrenom.size(); i++)
			tabNomPrenom[i] = NULL;

		// on copie les élément
		for(size_t i = 0; i < oldArray.size(); i++)
		{
			if(oldArray[i] != NULL && oldArray[i]->etat == ACTIVE)
			{
				int nouvellePosition = hashNomPrenom(oldArray[i]->nom, oldArray[i]->prenom, nbCollsions);
				tabNomPrenom[nouvellePosition] = oldArray[i];
			}
		}
	}

	/**
	 *  \fn Bottin::rehashTabTelephone(int& nbCollisions)
	 *  \param[in] nbCollisions
	 *  \param[out] nbCollisions à jour
	 */
	void Bottin::rehashTabTelephone(int& nbCollisions)
	{
		vector<EntreeHashTab*> ancienneTable(tabTelephone);

		//on agrandit la table de dispersion
		tabTelephone.resize(nextPrime(2*ancienneTable.size()));
		for (size_t i = 0 ; i < tabTelephone.size() ; i++)
		{
			tabTelephone[i] = NULL;
		}

		//copie les éléments de l'ancienne table à la nouvelle
		for (size_t i = 0; i < ancienneTable.size() ; i++)
		{
			if (ancienneTable[i] != NULL && ancienneTable[i]->etat==ACTIVE)
			{
				int nouvPosition = hashTelephone(ancienneTable[i]->tel, nbCollisions);
				tabTelephone[nouvPosition] = ancienneTable[i];
			}
		}
	}

	void Bottin::affiche() const
	{
		for(size_t i = 0; i < tabEntree.size(); i++)
		{
			if(tabEntree[i].nom != "")
			{
				cout << tabEntree[i].nom << ", " << tabEntree[i].prenom << endl;
				cout << tabEntree[i].tel << ", " << tabEntree[i].courriel << endl;
			}
		}
	}

	/**
	 *  \fn checkTel(const std::string& telephone)
	 *  \param[in] telephone: le numéro de tél à vérifier
	 *  \return vrai si le tél est du bon format, faux sinon
	 */
	bool Bottin::checkTelephone(const std::string& telephone) const
	{
		//format attendu: (999) 999-9999
		//si la longueur n'est pas 14 caractères
		if (telephone.size() != 14)
			return false;

		//si la longueur est bonne, on vérifie le placement des caractères
		if( telephone.at(0)=='(' && telephone.at(4)==')'&&telephone.at(5)==' '&&telephone.at(9)=='-'&&
				isdigit(telephone.at(1)) && isdigit(telephone.at(2)) && isdigit(telephone.at(3)) && isdigit(telephone.at(6)) &&
				isdigit(telephone.at(7)) && isdigit(telephone.at(8)) && isdigit(telephone.at(10)) && isdigit(telephone.at(11)) &&
				isdigit(telephone.at(12)) && isdigit(telephone.at(13)) )
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	std::string Bottin::convertTelephone(const std::string& telephone) const
	{
		//format attendu: (999) 999-9999
		//si la longueur est plus petite que 10, il y a moins que 10 chiffres assurément et c'est le mauvais format
		if (telephone.size() < 10)
			throw invalid_argument("numéro de téléphone invalide");

		//on boucle pour savoir combien de chiffres qu'il y a dans le numéro
		int digitCounter=0;
		std::string tempTelephone;
		std::string telephoneBonFormat;
		for (size_t i = 0 ; i < telephone.size(); i++)
		{
			if (isdigit(telephone.at(i)))
			{
				digitCounter++;
				tempTelephone.push_back(telephone.at(i));
			}
		}
		if (digitCounter!=10)
		{
			throw invalid_argument("numéro de téléphone invalide");
		}
		else
		{
			telephoneBonFormat.push_back('(');
			telephoneBonFormat.push_back(tempTelephone.at(0));
			telephoneBonFormat.push_back(tempTelephone.at(1));
			telephoneBonFormat.push_back(tempTelephone.at(2));
			telephoneBonFormat.push_back(')');
			telephoneBonFormat.push_back(' ');
			telephoneBonFormat.push_back(tempTelephone.at(3));
			telephoneBonFormat.push_back(tempTelephone.at(4));
			telephoneBonFormat.push_back(tempTelephone.at(5));
			telephoneBonFormat.push_back('-');
			telephoneBonFormat.push_back(tempTelephone.at(6));
			telephoneBonFormat.push_back(tempTelephone.at(7));
			telephoneBonFormat.push_back(tempTelephone.at(8));
			telephoneBonFormat.push_back(tempTelephone.at(9));
		}
		return telephoneBonFormat;
	}

	/**
	 * \fn int nextPrime (int n)
	 *
	 * \param[in] n Un nombre entier > 0
	 */
	int Bottin::nextPrime (int n)
	{
		if(n%2 == 0)
			n++;
		while(!isPrime(n))
			n+=2;
		return n;
	}

	/**
	 * \fn bool isPrime (int n)
	 *
	 * \param[in] n Un nombre entier > 0
	 * \return true ou false suivant que n est premier ou non
	 */
	bool Bottin::isPrime (int n)
	{
		if (n <= 1)
			return false;
		if (n == 2)              // le seul nombre premier pair
			return true;
		else if (n % 2 == 0)     // sinon, ce n'est pas un nombre premier
			return false;
		else
		{
			int divisor = 3;
			int upperLimit = static_cast<int>(sqrt((float)n) + 1);

			while (divisor <= upperLimit)
			{
				if (n % divisor == 0)
					return false;
				divisor +=2;
			}
			return true;
		}
	}

} //Fin du namespace
